/*********************
* Author:Dzlua
* QQ:505544956
* time:2016/06/27
*/
#include "Engine.h"
#include "../edui_sdl.h"
#include "edui/external/pcre++/pcre++.h"

#ifdef EDUI_USE_LUAPLUS
    #include "edui/lua/LuaWindow.h"
    #include "edui/lua/controls/LuaControl.h"
    #include "edui/lua/LuaFont.h"
    #include "edui/lua/LuaCursor.h"
    #include "edui/lua/LuaSurface.h"
    #include "edui/lua/LuaAnimation.h"
#endif

EDUI_NAMESPACE_BEGIN

// notice.
static Engine m_engine;

Engine::Engine()
{}

Engine::~Engine()
{
#ifdef EDUI_TEST_NEW_DELETE
    Log("[TEST] control new delete num : %d", Control::_GetNewDeleteNum());
    Log("[TEST] texture new delete num : %d", Texture::_GetNewDeleteNum());
    Log("[TEST] animation new delete num : %d", Animation::_GetNewDeleteNum());
    Log("[TEST] window new delete num : %d", Window::_GetNewDeleteNum());
    Log("[TEST] font new delete num : %d", Font::_GetNewDeleteNum());
    Log("[TEST] surface new delete num : %d", Surface::_GetNewDeleteNum());
    Log("[TEST] cursor new delete num : %d", Cursor::_GetNewDeleteNum());
    #ifdef EDUI_USE_LUAPLUS
        Log("[TEST] LuaControl new delete num : %d", LuaControl::_GetNewDeleteNum());
        Log("[TEST] LuaWindow new delete num : %d", LuaWindow::_GetNewDeleteNum());
        Log("[TEST] LuaFont new delete num : %d", LuaFont::_GetNewDeleteNum());
        Log("[TEST] LuaSurface new delete num : %d", LuaSurface::_GetNewDeleteNum());
        Log("[TEST] LuaCursor new delete num : %d", LuaCursor::_GetNewDeleteNum());
        Log("[TEST] LuaAnimation new delete num : %d", LuaAnimation::_GetNewDeleteNum());
    #endif
#endif
}

Engine* Engine::ToEngine(IEngine* engine)
{
    return dynamic_cast<Engine*>(engine);
}

const char* Engine::GetVersion()
{
    m_ver = IEngine::GetVersion();
    //edui,sdl
    SDL_version ver;
    SDL_GetVersion(&ver);
    m_ver.Format("%s, SDL %d.%d.%d", m_ver.GetString(), ver.major, ver.minor, ver.patch);
    //image
    const SDL_version* iver = IMG_Linked_Version();
    m_ver.Format("%s, SDL_IMG %d.%d.%d", m_ver.GetString(), iver->major, iver->minor, iver->patch);
    //ttf
    const SDL_version* tver = TTF_Linked_Version();
    m_ver.Format("%s, SDL_TTF %d.%d.%d", m_ver.GetString(), tver->major, tver->minor, tver->patch);

#ifdef EDUI_USE_LUAPLUS
    m_ver += ", Lua 5.1.4, LuaPlus51";
#endif
    m_ver += ", PCRE ";
    m_ver += pcre_version();

    return m_ver.GetString();
}

bool Engine::Init(const void* data1, const void* data2)
{
#ifdef EDUI_USE_LUAPLUS
    if (!m_luaMgr.Init()){
        Log("[EDUI] engine init : Unable to initialize LuaPlus.");
        this->Quit();
        return false;
    }
    Log("[EDUI] engine init : initialize LuaPlus ok.");
#endif

    if (SDL_Init(SDL_INIT_EVERYTHING) != 0){
        Log("engine init : Unable to initialize SDL2,%s",SDL_GetError());
        this->Quit();
        return false;
    }

    Log("[EDUI] engine init : initialize SDL2 ok.");

    if (IMG_Init(IMG_INIT_JPG | IMG_INIT_PNG | IMG_INIT_TIF | IMG_INIT_WEBP) == 0){
        Log("[EDUI] engine init : Unable to initialize SDL2_image.");
        this->Quit();
        return false;
    }

    Log("[EDUI] engine init : initialize SDL2_image ok.");

    if (TTF_Init() != 0){
        Log("[EDUI] engine init : Unable to initialize SDL2_ttf.");
        this->Quit();
        return false;
    }

    Log("[EDUI] engine init : initialize SDL2_ttf ok.");

    Log("[EDUI] engine init done!");

    this->onInit();

    return true;
}

void Engine::Quit()
{
    this->onQuit();
    
    //window
    WindowMgr* wndMgr = this->GetWindowMgr();
    for (size_t i = 0; i < wndMgr->Count(); ++i){
        IWindow* wnd = wndMgr->GetByIndex(i);
        if (wnd){
            wnd->Destroy();
            wnd->Release();
        }
    }
    wndMgr->Clear();

    //surface
    SurfaceMgr* surfaceMgr = this->GetSurfaceMgr();
    for (size_t i = 0; i < surfaceMgr->Count(); ++i){
        ISurface* surface = surfaceMgr->GetByIndex(i);
        if (surface){
            surface->Destroy();
            surface->Release();
        }
    }
    surfaceMgr->Clear();

    //fonts
    FontMgr* fontMgr = this->GetFontMgr();
    for (size_t i = 0; i < fontMgr->Count(); ++i){
        IFont* font = fontMgr->GetByIndex(i);
        if (font){
            font->Destroy();
            font->Release();
        }
    }
    fontMgr->Clear();

    //cursors
    CursorMgr* cursorMgr = this->GetCursorMgr();
    for (size_t i = 0; i < cursorMgr->Count(); ++i){
        ICursor* cursor = cursorMgr->GetByIndex(i);
        if (cursor){
            cursor->Destroy();
            cursor->Release();
        }
    }
    cursorMgr->Clear();

    //timer infos
    for (size_t i = 0; i < m_timerInfos.Count(); ++i){
        TimerInfo* timerinfo = m_timerInfos.GetByIndex(i);
        if (timerinfo){
            SDL_RemoveTimer(timerinfo->id);
            delete timerinfo;
        }
    }
    m_timerInfos.Clear();

    if (TTF_WasInit()){
        TTF_Quit();
        Log("[EDUI] engine quit : quit SDL2_ttf.");
    }

    IMG_Quit();
    Log("[EDUI] engine quit : quit SDL2_image.");

    if (SDL_WasInit(SDL_INIT_EVERYTHING)){
        SDL_Quit();
        Log("[EDUI] engine quit : quit SDL2.");
    }

#ifdef EDUI_USE_LUAPLUS
    m_luaMgr.Quit();
    Log("[EDUI] engine quit : quit LuaPlus.");
#endif

    Log("[EDUI] engine quit done.");
}

IWindow* Engine::GetWindowBySDLWindow(SDL_Window* wnd)
{
    WindowMgr* mgr = this->GetWindowMgr();
    for (size_t i = 0; i < mgr->Count(); ++i){
        Window* wnd1 = Window::ToWindow(mgr->GetByIndex(i));
        if (wnd1 && (wnd1->GetSDLWindow() == wnd)){
            return wnd1;
        }
    }
    return 0;
}

IWindow* Engine::GetKeyFocusWindow()
{
    SDL_Window* wnd = SDL_GetKeyboardFocus();
    if (wnd){
        return this->GetWindowBySDLWindow(wnd);
    }
    return 0;
}

IWindow* Engine::GetMouseFocusWindow()
{
    SDL_Window* wnd = SDL_GetMouseFocus();
    if (wnd){
        return this->GetWindowBySDLWindow(wnd);
    }
    return 0;
}

bool Engine::SetClipboardText(const char* text)
{
    return !SDL_SetClipboardText(text);
}

char* Engine::GetClipboardText()
{
    return SDL_GetClipboardText();
}

bool Engine::HasClipboardText()
{
    return !!SDL_HasClipboardText();
}

void Engine::SetCursor(ICursor* cursor)
{
    if (cursor){
        SDL_Cursor* cor = Cursor::ToCursor(cursor)->GetSDLCursor();
        if (SDL_GetCursor() == cor) return;
        SDL_SetCursor(cor);
    } else {
        SDL_Cursor* cor = SDL_GetDefaultCursor();
        if (SDL_GetCursor() == cor) return;
        SDL_SetCursor(cor);
    }
}

void Engine::ShowCursor(bool show)
{
    SDL_ShowCursor(show ? SDL_ENABLE : SDL_DISABLE);
}

bool Engine::IsCursorShow()
{
    return SDL_ENABLE == SDL_ShowCursor(SDL_QUERY);
}

Uint32 Engine_TimerCallback(Uint32 interval, void *param)
{
    //cancel
    if (!param) return 0;
    TimerInfo *timerinfo = (TimerInfo*)param;
    if (timerinfo->wnd){
        return timerinfo->callback(timerinfo->id, interval, timerinfo->wnd, timerinfo->data1, timerinfo->data2);
    } else {
        return timerinfo->callback(timerinfo->id, interval, timerinfo->wnd, timerinfo->data1, timerinfo->data2);
    }
}

TimerID Engine::AddTimer(Uint32 interval, TimerCallback callback,IWindow* wnd, void *data1, void *data2)
{
    TimerInfo* timerinfo = new TimerInfo;
    Assert(timerinfo);
    TimerID id = SDL_AddTimer(interval, Engine_TimerCallback, timerinfo);
    if (id == 0){
        delete timerinfo;
        return 0;
    }
    timerinfo->wnd = wnd;
    timerinfo->id = id;
    timerinfo->callback = callback;
    timerinfo->data1 = data1;
    timerinfo->data2 = data2;
    m_timerInfos.Add(timerinfo);
    return id;
}

void Engine::RemoveTimer(TimerID id)
{
    TimerInfo* timerinfo = 0;
    for (size_t i = 0; i < m_timerInfos.Count(); ++i){
        timerinfo = m_timerInfos.GetByIndex(i);
        if (timerinfo && timerinfo->id == id){
            break;
        }
    }

    if (timerinfo){
        SDL_RemoveTimer(id);
        m_timerInfos.Remove(timerinfo);
        delete timerinfo;
    }
}

void Engine::RemoveTimer(IWindow *wnd) {
    MgrSimple<TimerInfo> timerInfos;
    TimerInfo* timerinfo = 0;
    for (size_t i = 0; i < m_timerInfos.Count(); ++i){
        timerinfo = m_timerInfos.GetByIndex(i);
        if (timerinfo && timerinfo->wnd == wnd){
            timerInfos.Add(timerinfo);
        }
    }

    for (size_t i = 0; i < timerInfos.Count(); ++i){
        timerinfo = timerInfos.GetByIndex(i);
        if (timerinfo && timerinfo->wnd == wnd){
            this->RemoveTimer(timerinfo->id);
        }
    }
}


/* fps:
*       < 0 : waite for msg.
*       = 0 : as fast as possible.
*       > 0 : fixed fps.
*/
void Engine::RunMsgLoop(float fps)
{
    SDL_Event evt;
    WindowMgr* mgr = this->GetWindowMgr();
    if (fps<0.f){
        Log("[EDUI] engine RunMsgLoop : wait for msg, type 1", fps);
        while (mgr->Count() > 0){
            if (SDL_WaitEvent(&evt)){
                this->HandleSDLMsg(&evt);
            }
        }
    } else if (fps==0.f) {
        Log("[EDUI] engine RunMsgLoop : fps-> %f, type 2", fps);
        while (mgr->Count() > 0){
            if (SDL_PollEvent(&evt)){
                this->HandleSDLMsg(&evt);
            }
            // render all window
            for (size_t i = 0; i < mgr->Count(); ++i){
                IWindow* wnd = mgr->GetByIndex(i);
                if (wnd){
                    wnd->Invaldate(0);
                }
            }
        }
    } else {
        Log("[EDUI] engine RunMsgLoop : fps-> %f, type 3, param fps used as delay time", fps);
        while (mgr->Count() > 0){
            if (SDL_PollEvent(&evt)){
                this->HandleSDLMsg(&evt);
            }
            // render all window
            for (size_t i = 0; i < mgr->Count(); ++i){
                IWindow* wnd = mgr->GetByIndex(i);
                if (wnd){
                    wnd->Invaldate(0);
                }
            }
            SDL_Delay((int)fps);
        }
    }
}

/* handle SDL2 msg and dispatch it to edui msg
*   Engine::HandleSDLMsg() -> IEngine::DispatchMsg() -> IWindow::HandleMsg()
*   SDL2 msg                    edui msg                    handle edui msg.
*/
void Engine::HandleSDLMsg(SDL_Event* event)
{
    Msg msg;
    msg.common.msg = MSG_NULL;
    msg.common.wnd = 0;

    WindowMgr* wndMgr = this->GetWindowMgr();
    // window msg.
    if (event->type == SDL_WINDOWEVENT){
        msg.wnd.wnd = wndMgr->GetByID(event->window.windowID);
        msg.wnd.data1 = event->window.data1;
        msg.wnd.data2 = event->window.data2;
        msg.wnd.msg = MSG_NULL;
        switch(event->window.event){
        case SDL_WINDOWEVENT_SHOWN:
            msg.wnd.msg = MSG_WND_SHOW;
            break;
        case SDL_WINDOWEVENT_HIDDEN:
            msg.wnd.msg = MSG_WND_HIDDEN;
            break;
        case SDL_WINDOWEVENT_EXPOSED:
            msg.wnd.msg = MSG_WND_EXPOSED;
            break;
        case SDL_WINDOWEVENT_MOVED:
            msg.wnd.msg = MSG_WND_MOVED;
            break;
        case SDL_WINDOWEVENT_RESIZED:
            msg.wnd.msg = MSG_WND_RESIZED;
            break;
        case SDL_WINDOWEVENT_SIZE_CHANGED:
            msg.wnd.msg = MSG_WND_SIZE_CHANGED;
            break;
        case SDL_WINDOWEVENT_MINIMIZED:
            msg.wnd.msg = MSG_WND_MINIMIZED;
            break;
        case SDL_WINDOWEVENT_MAXIMIZED:
            msg.wnd.msg = MSG_WND_MAXIMIZED;
            break;
        case SDL_WINDOWEVENT_RESTORED:
            msg.wnd.msg = MSG_WND_RESTORED;
            break;
        case SDL_WINDOWEVENT_ENTER:
            msg.wnd.msg = MSG_WND_ENTER;
            break;
        case SDL_WINDOWEVENT_LEAVE:
            msg.wnd.msg = MSG_WND_LEAVE;
            break;
        case SDL_WINDOWEVENT_FOCUS_GAINED:
            msg.wnd.msg = MSG_WND_FOCUS_GAINED;
            break;
        case SDL_WINDOWEVENT_FOCUS_LOST:
            msg.wnd.msg = MSG_WND_FOCUS_LOST;
            break;
        case SDL_WINDOWEVENT_CLOSE:
            msg.wnd.msg = MSG_WND_CLOSE;
            break;
        case SDL_WINDOWEVENT_HIT_TEST:
            // not handle hit_test event temporarily.
            break;
        default:
            LogDebug("[EDUI] engine HandleSDLMsg : unknown SDL2 event %d", event->window.event);
            return;
        }
        if (msg.wnd.msg){
            this->DispatchMsg(&msg);
        }      
        return;
    }
    // mouse msg
    else if (event->type == SDL_MOUSEMOTION){
        msg.motion.wnd = wndMgr->GetByID(event->motion.windowID);
        msg.motion.which = event->motion.which;
        msg.motion.state = event->motion.state;
        msg.motion.x = event->motion.x;
        msg.motion.y = event->motion.y;
        msg.motion.xrel = event->motion.xrel;
        msg.motion.yrel = event->motion.yrel;
        msg.motion.msg = MSG_MOUSE_MOVE;
        this->DispatchMsg(&msg);
        return;
    } else if (event->type == SDL_MOUSEBUTTONDOWN || event->type == SDL_MOUSEBUTTONUP){
        msg.button.wnd = wndMgr->GetByID(event->button.windowID);
        msg.button.which = event->button.which;
        msg.button.button = event->button.button;
        msg.button.state = event->button.state;
        msg.button.clicks = event->button.clicks;
        msg.button.x = event->button.x;
        msg.button.y = event->button.y;
        if (event->type == SDL_MOUSEBUTTONDOWN){
            msg.button.msg = MSG_MOUSE_BUTTON_DOWN;
        } else {
            msg.button.msg = MSG_MOUSE_BUTTON_UP;
        }
        this->DispatchMsg(&msg);
        return;
    } else if (event->type == SDL_MOUSEWHEEL){
        msg.wheel.wnd = wndMgr->GetByID(event->wheel.windowID);
        msg.wheel.which = event->wheel.which;
        msg.wheel.x = event->wheel.x;
        msg.wheel.y = event->wheel.y;
        msg.wheel.direction = (MouseWheelDirection)event->wheel.direction;
        msg.wheel.msg = MSG_MOUSE_WHEEL;
        this->DispatchMsg(&msg);
        return;
    }
    // keyboard msg
    else if (event->type == SDL_KEYDOWN || event->type == SDL_KEYUP){
        msg.key.wnd = wndMgr->GetByID(event->key.windowID);
        msg.key.state = event->key.state;
        msg.key.repeat = event->key.repeat;
        msg.key.mod = event->key.keysym.mod;
        msg.key.key = event->key.keysym.sym;
        msg.key.scan = (Scancode)event->key.keysym.scancode;
        if (event->type == SDL_KEYDOWN){
            msg.key.msg = MSG_KEY_DOWN;
        } else {
            msg.key.msg = MSG_KEY_UP;
        }
        this->DispatchMsg(&msg);
        return;
    }
    // text msg
    else if (event->type == SDL_TEXTEDITING){
        msg.text.wnd = wndMgr->GetByID(event->edit.windowID);
        strcpy(msg.text.text, event->edit.text);
        msg.text.start = event->edit.start;
        msg.text.length = event->edit.length;
        msg.text.msg = MSG_TEXT_EDITING;
        this->DispatchMsg(&msg);
        return;
    } else if (event->type == SDL_TEXTINPUT){
        msg.text.wnd = wndMgr->GetByID(event->text.windowID);
        strcpy(msg.text.text, event->text.text);
        msg.text.msg = MSG_TEXT_INPUT;
        this->DispatchMsg(&msg);
        return;
    }
    // drop msg
    else if (event->type >= SDL_DROPFILE && event->type <= SDL_DROPCOMPLETE){
        msg.drop.msg = (MsgType)event->type;
        msg.drop.wnd = this->GetMouseFocusWindow();
        if (event->type == SDL_DROPFILE || event->type == SDL_DROPTEXT){
            strcpy(msg.drop.file, event->drop.file);
            SDL_free(event->drop.file);
        }
        this->DispatchMsg(&msg);
        return;
    }
    // user msg
    else if (event->type >= SDL_USEREVENT){
        msg.user.wnd = wndMgr->GetByID(event->user.windowID);
        msg.user.code = event->user.code;
        msg.user.data1 = event->user.data1;
        msg.user.data2 = event->user.data2;
        msg.user.msg = (MsgType)event->type;
        this->DispatchMsg(&msg);
        return;
    }
    // quit msg,can not go there.
    else if (event->type == SDL_QUIT){
        msg.msg = MSG_QUIT;
        this->DispatchMsg(&msg);
        LogDebug("[EDUI] engine HandleSDLMsg : SDL2 event SDL_QUIT");
        return;
    } else {
        LogDebug("[EDUI] engine HandleSDLMsg : SDL2 event %d, unhandled in EDUI", event->type);
    }
}

void Engine::PushMsg(Msg *msg)
{
    SDL_Event event;
    if (msg->msg == MSG_QUIT){
        // quit msg
        event.type = SDL_QUIT;
    } else if (msg->msg >= MSG_WND_SHOW && msg->msg <= MSG_WND_CLOSE){
        // window msg
        WndMsg *evt = &msg->wnd;
        event.type = SDL_WINDOWEVENT;
        event.window.data1 = evt->data1;
        event.window.data2 = evt->data2;
        event.window.windowID = evt->wnd->GetID();
        event.window.event = msg->msg - MSG_WND_SHOW + 1;
    } else if (msg->msg == MSG_KEY_DOWN || msg->msg == MSG_KEY_UP){
        // key msg
        KeyboardMsg *evt = &msg->key;
        if (msg->msg == MSG_KEY_DOWN){
            event.type = SDL_KEYDOWN;
        } else {
            event.type = SDL_KEYUP;
        }
        event.key.windowID = evt->wnd->GetID();
        event.key.state = evt->state;
        event.key.repeat = evt->repeat;
        event.key.keysym.mod = evt->mod;
        event.key.keysym.sym = evt->key;
        event.key.keysym.scancode = (SDL_Scancode)evt->scan;
    } else if (msg->msg == MSG_TEXT_EDITING || msg->msg == MSG_TEXT_INPUT){
        // key text msg
        TextMsg *evt = &msg->text;
        if (msg->msg == MSG_TEXT_EDITING){
            event.type = SDL_TEXTEDITING;
            event.edit.windowID = evt->wnd->GetID();
            event.edit.start = evt->start;
            event.edit.length = evt->length;
            strcpy(event.edit.text, evt->text);
        } else {
            event.type = SDL_TEXTINPUT;
            event.text.windowID = evt->wnd->GetID();
            strcpy(event.text.text, evt->text);
        }
    } else if (msg->msg == MSG_MOUSE_MOVE){
        // mouse move msg
        MouseMoveMsg *evt = &msg->motion;
        event.type = SDL_MOUSEMOTION;
        event.motion.windowID = evt->wnd->GetID();
        event.motion.which = evt->which;
        event.motion.state = evt->state;
        event.motion.x = evt->x;
        event.motion.y = evt->y;
        event.motion.xrel = evt->xrel;
        event.motion.yrel = evt->yrel;
    } else if (msg->msg == MSG_MOUSE_BUTTON_DOWN || msg->msg == MSG_MOUSE_BUTTON_UP){
        // mouse button msg
        MouseButtonMsg *evt = &msg->button;
        if (msg->msg == MSG_MOUSE_BUTTON_DOWN){
            event.type = SDL_MOUSEBUTTONDOWN;
        } else {
            event.type = SDL_MOUSEBUTTONUP;
        }
        event.button.windowID = evt->wnd->GetID();
        event.button.which = evt->which;
        event.button.button = evt->button;
        event.button.state = evt->state;
        event.button.clicks = evt->clicks;
        event.button.x = evt->x;
        event.button.y = evt->y;
    } else if (msg->msg == MSG_MOUSE_WHEEL){
        // mouse whell msg
        MouseWheelMsg *evt = &msg->wheel;
        event.type = SDL_MOUSEWHEEL;
        event.wheel.windowID = evt->wnd->GetID();
        event.wheel.which = evt->which;
        event.wheel.x = evt->x;
        event.wheel.y = evt->y;
        event.wheel.direction = evt->direction;
    } else if (msg->msg >= MSG_DROPFILE && msg->msg <= MSG_DROPCOMPLETE){
        // drop file msg
        DropFileMsg *evt = &msg->drop;
        event.type = msg->msg;
        if (msg->msg == MSG_DROPFILE || msg->msg == MSG_DROPTEXT){
            event.drop.file = (char*)SDL_malloc(strlen(evt->file) + 1);
            strcpy(event.drop.file, evt->file);
            event.drop.file[strlen(evt->file)+1] = 0;
        }
    } else if (msg->msg >= MSG_USERMSG){
        // user msg
        UserMsg *evt = &msg->user;
        event.type = msg->msg;
        event.user.windowID = evt->wnd->GetID();
        event.user.code = evt->code;
        event.user.data1 = evt->data1;
        event.user.data2 = evt->data2;
    }
    SDL_PushEvent(&event);
}

IWindow* Engine::GetWindowInterface()
{
    return Window::GetInterface();
}

ITexture* Engine::GetTextureInterface()
{
    return Texture::GetInterface();
}

ISurface* Engine::GetSurfaceInterface()
{
    return Surface::GetInterface();
}

IFont* Engine::GetFontInterface()
{
    return Font::GetInterface();
}

ICursor* Engine::GetCursorInterface()
{
    return Cursor::GetInterface();
}

void Engine::onInit()
{}

void Engine::onQuit()
{}

EDUI_NAMESPACE_END